// PanelTreeBrowser.cpp : implementation file
//

#include "StdAfx.h"
#include "PanelTreeBrowser.h"
#include "PanelPreview.h"

#include "ViewManager.h"
#include "Viewport.h"

#include "Objects\EntityScript.h"

#include "EntityPrototype.h"
#include "EntityPrototypeLibrary.h"
#include "EntityPrototypeManager.h"
#include "Prefabs\PrefabManager.h"
#include "ProceduralSystem/ProceduralCreation.h"
#include "IThreadTask.h"

#include <io.h>
#include <StringUtils.h>

CPanelTreeBrowser::TSearchSpecAndItemNameMap CPanelTreeBrowser::m_lastSelectedItemNames;

// Sort the item in reverse alphabetical order.
static int CALLBACK TreeCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	// lParamSort contains a pointer to the tree control.
	// The lParam of an item is just its handle, as specified with SetItemData
	CTreeCtrl* pTreeCtrl = (CTreeCtrl*)lParamSort;

	int nChilds1 = (pTreeCtrl->ItemHasChildren((HTREEITEM)lParam1) == TRUE) ? 1 : 0;
	int nChilds2 = (pTreeCtrl->ItemHasChildren((HTREEITEM)lParam2) == TRUE) ? 1 : 0;
	if (nChilds1 != nChilds2)
		return nChilds2 - nChilds1;

	CString strItem1 = pTreeCtrl->GetItemText((HTREEITEM) lParam1);
	CString strItem2 = pTreeCtrl->GetItemText((HTREEITEM) lParam2);

	return stricmp(strItem1, strItem2);
}

//-----------------------------------------------------------------------------

CObjectBrowserFileScanner::CObjectBrowserFileScanner()
{
	m_bScanning = false;
	m_bNewFiles = false;
}

void CObjectBrowserFileScanner::Scan( const CString& searchSpec )
{
	if( !m_bScanning )
	{
		m_searchSpec = searchSpec;
		m_bScanning = true;
		m_bNewFiles = false;

		CString searchPath = Path::GetPath( m_searchSpec );
		searchPath.Replace( '/','\\' );
		searchPath = Path::AddBackslash( searchPath );
		CString fileSpec = m_searchSpec.Mid( searchPath.GetLength() );
		int nToken = 0;
		CString token = fileSpec.Tokenize( ";", nToken );

		m_files.reserve( kDefaultAllocFileCount );

		while( !token.IsEmpty() )
		{
			CString fileSearchSpec = searchPath;

			fileSearchSpec += token;
			CFileUtil::ScanDirectory( "", fileSearchSpec, m_files, true, false );

			if( !m_files.empty() )
			{
				CryAutoCriticalSection lock( m_lock );

				for( size_t i = 0, iCount = m_files.size(); i < iCount; ++i )
				{
					m_files[i].filename = Path::ToUnixPath( m_files[i].filename );
				}

				m_filesForUser.insert( m_filesForUser.end(), m_files.begin(), m_files.end() );
				m_files.clear();
				m_bNewFiles = true;
			}

			token = fileSpec.Tokenize( ";", nToken );
		}

		m_bScanning = false;
	}
}

bool CObjectBrowserFileScanner::HasNewFiles()
{
	return m_bNewFiles;
}

void CObjectBrowserFileScanner::GetScannedFiles( CFileUtil::FileArray &files )
{
	CryAutoCriticalSection lock( m_lock );

	files.swap( m_filesForUser );
	m_filesForUser.clear();
	m_bNewFiles = false;
}

bool CObjectBrowserFileScanner::IsScanningForFiles()
{
	return m_bScanning;
}

/////////////////////////////////////////////////////////////////////////////
// CPanelTreeBrowser dialog

CPanelTreeBrowser::TFileHistory CPanelTreeBrowser::sm_fileHistory;

CPanelTreeBrowser::CPanelTreeBrowser(CWnd* pParent /*=NULL*/)
	: CXTResizeDialog(CPanelTreeBrowser::IDD, pParent)
{
	//{{AFX_DATA_INIT(CPanelTreeBrowser)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

	m_panelPreview = NULL;
	m_panelPreviewId = 0;
	m_dialogType=DEFAULT_DIALOG;
	m_nNumFiles = 0;
	GetIEditor()->RegisterNotifyListener(this);
}

CPanelTreeBrowser::~CPanelTreeBrowser()
{
	m_treeCtrl.DeleteAllItems();

	GetIEditor()->UnregisterNotifyListener(this);
	if (m_panelPreviewId)
		GetIEditor()->RemoveRollUpPage( ROLLUP_OBJECTS,m_panelPreviewId );
	m_panelPreviewId = 0;
	m_panelPreview = 0;
}

void CPanelTreeBrowser::DoDataExchange(CDataExchange* pDX)
{
	CXTResizeDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_RELOAD,m_reloadBtn );
	DDX_Control(pDX, IDC_CUSTOMBTN,m_customBtn );
	DDX_Control(pDX, IDC_FILTER,m_filter );
}


BEGIN_MESSAGE_MAP(CPanelTreeBrowser, CXTResizeDialog)
	ON_BN_CLICKED(IDC_RELOAD, OnReload)
	ON_BN_CLICKED(IDC_CUSTOMBTN, OnCustomBtnClick)
	ON_EN_CHANGE(IDC_FILTER,OnFilterChange)
	ON_WM_DESTROY()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::SetEnable( bool bEnable)
{
	GetDlgItem(IDC_BROWSER_TREE)->EnableWindow(bEnable);
	GetDlgItem(IDC_RELOAD)->EnableWindow(bEnable);
	GetDlgItem(IDC_FILTER)->EnableWindow(bEnable);
}

/////////////////////////////////////////////////////////////////////////////
// CPanelTreeBrowser message handlers

BOOL CPanelTreeBrowser::OnInitDialog() 
{
	CXTResizeDialog::OnInitDialog();
	CMFCUtils::LoadTrueColorImageList( m_cImageList,IDB_TREE_VIEW,16,RGB(255,0,255) );

	if (GetDialogType()!=DEFAULT_DIALOG)
	{
		if (GetDialogType()==ME_SELECTION_DIALOG)
			m_customBtn.SetWindowText("Select");
		else
			if (GetDialogType()==ME_REPLACE_DIALOG)
				m_customBtn.SetWindowText("Replace");

		m_customBtn.ShowWindow(SW_SHOW);
	}

	CRect rc;
	GetClientRect(rc);

	rc.DeflateRect(5,5,5,64);

	m_treeCtrl.Create(WS_CHILD|WS_VISIBLE|WS_BORDER,rc,this,IDC_BROWSER_TREE );
	m_treeCtrl.EnableAutoNameGrouping(true,0);
	m_treeCtrl.SetImageList( &m_cImageList );
	m_treeCtrl.SetCallback( CTreeCtrlReport::eCB_OnSelect,functor(*this,&CPanelTreeBrowser::OnSelectionChanged) );
	m_treeCtrl.SetCallback( CTreeCtrlReport::eCB_OnDragAndDrop,functor(*this,&CPanelTreeBrowser::OnDragAndDrop) );
	m_treeCtrl.SetCallback( CTreeCtrlReport::eCB_OnDblClick,functor(*this,&CPanelTreeBrowser::OnDblclkBrowserTree) );

	CXTPReportColumn *pCol1 = m_treeCtrl.AddTreeColumn( "Browser" );
	pCol1->SetSortable(TRUE);
	m_treeCtrl.GetColumns()->SetSortColumn(pCol1,TRUE);
	m_treeCtrl.SetExpandOnDblClick(true);

	SetResize( IDC_BROWSER_TREE,SZ_RESIZE(1) );
	SetResize( IDC_FILTER,SZ_RESIZE(1) );

	return TRUE;
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnDestroy()
{
	CXTResizeDialog::OnDestroy();
}

void CPanelTreeBrowser::Create( TSelectCallback &cb,const CString &searchSpec,CWnd *parent,int flags )
{
	CXTResizeDialog::Create( IDD,parent );

	SetResize( IDC_RELOAD,SZ_HORREPOS(1) );
	SetResize( IDC_CUSTOMBTN,SZ_HORREPOS(1) );

	m_flags = flags;
	m_bSelectOnClick = flags & SELECT_ONCLICK;
	m_bDragDropEnabled = !(flags & NO_DRAGDROP);

	m_searchSpec = Path::ToUnixPath(searchSpec);
	Refresh(false);

	m_selectCallback = cb;
}

void CPanelTreeBrowser::ClearTreeItems()
{
	CString searchPath = Path::GetPath( m_searchSpec );
	CString fileSpec = m_searchSpec.Mid( searchPath.GetLength() );

	m_treeCtrl.DeleteAllItems();
	m_treeCtrl.GetRecords()->RemoveAll();
	m_nNumFiles = 0;
	m_path = searchPath;
	SetDlgItemText( IDC_NUM_FILES, "No Items" );
}

void CPanelTreeBrowser::ReselectLastSelectedTreeItem()
{
	if( !m_lastSelectedItemNames[m_searchSpec].first.IsEmpty() )
	{
		TSelectCallback cb = m_selectCallback;
		
		m_selectCallback = 0;

		if( m_lastSelectedItemNames[m_searchSpec].second == eItemNodeType_File )
		{
			m_treeCtrl.SelectRecordByUserString( m_lastSelectedItemNames[m_searchSpec].first.GetBuffer() );
		}
		else
		if( m_lastSelectedItemNames[m_searchSpec].second == eItemNodeType_Folder )
		{
			m_treeCtrl.SelectRecordByPath( m_lastSelectedItemNames[m_searchSpec].first.GetBuffer() );
		}

		m_selectCallback = cb;
	}
}

void CPanelTreeBrowser::LoadFilesFromScanning()
{
	if( m_fileScanner.HasNewFiles() )
	{
		CFileUtil::FileArray files;
		m_fileScanner.GetScannedFiles( files );

		FillTreeWithFiles( files );
		TFileHistory::iterator it = sm_fileHistory.find( m_searchSpec );

		if( it != sm_fileHistory.end() )
		{
			CFileUtil::FileArray& existingFiles = it->second;

			existingFiles.insert( existingFiles.end(), files.begin(), files.end() );
		}
		else
		{
			sm_fileHistory[m_searchSpec] = files;
		}

		UpdateFileCountLabel();
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::Refresh( bool bReloadFiles )
{
	CWaitCursor wait;

	if (m_searchSpec == "*EntityClass")
	{
		ClearTreeItems();
		FillEntityScripts(bReloadFiles);
	}
	else if (m_searchSpec == "*EntityArchetype")
	{
		ClearTreeItems();
		FillDBLibrary( EDB_TYPE_ENTITY_ARCHETYPE );
	}
	else if (m_searchSpec == "*Prefabs")
	{
		ClearTreeItems();
		FillDBLibrary( EDB_TYPE_PREFAB );
	}
	else
	{
		TFileHistory::iterator it = sm_fileHistory.find( m_searchSpec );

		if( it == sm_fileHistory.end() || bReloadFiles )
		{
			if( m_fileScanner.IsScanningForFiles() )
				return;

			if( it != sm_fileHistory.end() )
			{
				sm_fileHistory.erase( it );
			}

			ClearTreeItems();
			m_fileScanner.Scan( m_searchSpec );
			LoadFilesFromScanning();
		}
		else
		{
			ClearTreeItems();
			FillTreeWithFiles( it->second );
			UpdateFileCountLabel();
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::AddPreviewPanel()
{
	if (m_panelPreview)
		return;
	if (!(m_flags & NO_PREVIEW) && gSettings.bPreviewGeometryWindow)
	{
		// Searching geometries.
		if (strstr(m_searchSpec,"*.chr") != 0 || strstr(m_searchSpec,"*.cgf") != 0 || strstr(m_searchSpec,"*.cga") != 0)
		{
			// Create Preview.
			m_panelPreview = new CPanelPreview( AfxGetMainWnd() );
			m_panelPreviewId = GetIEditor()->AddRollUpPage( ROLLUP_OBJECTS,"Object Preview",m_panelPreview );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::AddModularEditorPreviewPanel(CProceduralCreationDialog *procDialog, CRollupCtrl *previewCtrl)
{
	if (m_panelPreview)
		return;

	// Create Preview.
	m_panelPreview = new CPanelPreview( procDialog );
	previewCtrl->InsertPage("Object Preview",m_panelPreview);
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::SelectFile( const CString &filename )
{
	TSelectCallback cb = m_selectCallback;
	m_selectCallback = 0;
	m_treeCtrl.SelectRecordByUserString( Path::ToUnixPath(filename) );
	m_selectCallback = cb;
}

inline bool SortEntityScripts( CEntityScript *pScript1,CEntityScript *pScript2 )
{
	return pScript1->GetFile() < pScript2->GetFile();
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::FillEntityScripts( bool bReload )
{
	if (bReload)
		CEntityScriptRegistry::Instance()->Reload();

	// Entity scripts.
	std::vector<CEntityScript*> scripts;
	CEntityScriptRegistry::Instance()->GetScripts( scripts );
	std::sort( scripts.begin(),scripts.end(),SortEntityScripts );

	m_treeCtrl.BeginUpdate();
	for (size_t i = 0; i < scripts.size(); i++)
	{
		// If class is not usable simply skip it.
		if (!scripts[i]->IsUsable())
			continue;

		CString name = scripts[i]->GetName();
		CString clsFile = scripts[i]->GetFile();

		const SEditorClassInfo& editorClassInfo = scripts[i]->GetClass()->GetEditorClassInfo();
		CString clsCategory = editorClassInfo.sCategory;

		if(!clsCategory.IsEmpty())
		{
			clsFile = clsCategory + "/" + name;
		}
		else if (clsFile.IsEmpty())
		{
			clsFile= "Default/" + name;
		}
		else
		{
			clsFile = Path::ReplaceFilename(clsFile,name,clsFile);
			clsFile.Replace( "Scripts/Entities/","" );
		}

		CTreeItemRecord *pRec = new CTreeItemRecord(false,Path::RemoveExtension(clsFile));
		pRec->SetUserString( scripts[i]->GetName() );
		pRec->SetIcon(-1);
		m_treeCtrl.AddTreeRecord(pRec,0);
	}

	m_treeCtrl.EndUpdate();
	m_treeCtrl.Populate();
	ReselectLastSelectedTreeItem();

	CString numFiles;
	numFiles.Format( "%d Entities",scripts.size() );
	SetDlgItemText( IDC_NUM_FILES,numFiles );
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::FillDBLibrary( EDataBaseItemType dbType )
{
	m_treeCtrl.BeginUpdate();

	int nNumItems = 0;

	IDataBaseManager *pManager = GetIEditor()->GetDBItemManager(dbType);
	for (int j = 0; j < pManager->GetLibraryCount(); j++)
	{
		IDataBaseLibrary *lib = pManager->GetLibrary(j);

		for (int i = 0; i < lib->GetItemCount(); i++)
		{
			IDataBaseItem *pItem = lib->GetItem(i);

			CTreeItemRecord *pRec = new CTreeItemRecord(false,pItem->GetFullName());
			pRec->SetUserString( GuidUtil::ToString(pItem->GetGUID()) );
			pRec->SetIcon(-1);
			m_treeCtrl.AddTreeRecord(pRec,0);

			nNumItems++;
		}
	}

	m_treeCtrl.EndUpdate();
	m_treeCtrl.Populate();
	ReselectLastSelectedTreeItem();

	CString numFiles;
	numFiles.Format( "%d Items",nNumItems );
	SetDlgItemText( IDC_NUM_FILES,numFiles );
}

void CPanelTreeBrowser::UpdateFileCountLabel()
{
	CString strNumFiles;
	TFileHistory::iterator it = sm_fileHistory.find( m_searchSpec );

	if( it != sm_fileHistory.end() )
	{
		m_nNumFiles = sm_fileHistory[m_searchSpec].size();
	}
	else
	{
		m_nNumFiles = 0;
	}

	strNumFiles.Format( "%d Files", m_nNumFiles );
	SetDlgItemText( IDC_NUM_FILES, strNumFiles );
	//AfxMessageBox( strNumFiles );
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::FillTreeWithFiles( CFileUtil::FileArray &files )
{
	m_treeCtrl.BeginUpdate();

	for (size_t i = 0, iCount = files.size(); i < iCount; i++)
	{
		CString filename = files[i].filename;

		// Do not show _LODn geometries in the object browser
		if (filename.MakeLower().Find("_lod") !=-1 )
			continue;

		CTreeItemRecord *pRec = new CTreeItemRecord( false, Path::RemoveExtension( filename ) );
		pRec->SetUserString( filename );
		pRec->SetIcon( -1 );
		m_treeCtrl.AddTreeRecord( pRec, 0 );
	}

	m_treeCtrl.EndUpdate();
	m_treeCtrl.Populate();
	ReselectLastSelectedTreeItem();
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnDblclkBrowserTree( CTreeItemRecord *pRec )
{
	CString file = GetSelectedFile();
	if (file.IsEmpty())
		return;

	AcceptFile( file,false );
}

//////////////////////////////////////////////////////////////////////////
CString CPanelTreeBrowser::GetSelectedFile()
{
	CTreeCtrlReport::Records records;
	m_treeCtrl.GetSelectedRecords( records );
	if (records.size() > 0)
	{
		return records[0]->GetUserString();
	}
	return "";
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::AcceptFile( const CString &file,bool bDragAndDrop )
{
	if (!file.IsEmpty())
	{
		// Select this file.
		if (m_selectCallback && !bDragAndDrop)
		{
			m_selectCallback( file );
		}
		if (m_dragAndDropCallback && bDragAndDrop)
		{
			m_dragAndDropCallback( file );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnSelectionChanged( CTreeItemRecord *pRec )
{
	CString itemID = GetSelectedFile();

	// If geometry update preview.
	if (m_panelPreview)
	{
		if (!itemID.IsEmpty())
		{
			// Check if preview panel is expended.
			m_panelPreview->LoadFile( itemID );
		}
	}
	if (m_bSelectOnClick)
	{
		if (!itemID.IsEmpty())
		{
			if (m_selectCallback)
			{
				m_selectCallback( itemID );

				// A building must be updated in case the selected object is an object from building
				CProceduralCreationWorks::SetBuildingModified(GetIEditor()->GetSelectedObject());
				CProceduralCreationWorks::UpdatateBuildingPrefab(GetIEditor()->GetSelection(),0);
			}
		}
	}

	// this is a folder, find path and save
	if( itemID.IsEmpty() )
	{
		CString path;

		m_treeCtrl.CalculateItemPath( pRec, path );
		m_lastSelectedItemNames[m_searchSpec] = std::make_pair( path, eItemNodeType_Folder );
	}
	else
	{
		m_lastSelectedItemNames[m_searchSpec] = std::make_pair( itemID, eItemNodeType_File );
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnDragAndDrop( CTreeItemRecord *pRec )
{
	CPoint pt;
	GetCursorPos(&pt);
	if (m_bDragDropEnabled)
	{
		CViewport *pView = GetIEditor()->GetViewManager()->GetViewportAtPoint(pt);
		if (pView)
		{
			// Drag and drop into one of views.
			// Start object creation.
			AcceptFile( pRec->GetUserString(),true );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnReload()
{
	m_filter.SetWindowText( "" );
	Refresh(true);
}

void CPanelTreeBrowser::OnCustomBtnClick()
{
	CString file = GetSelectedFile();
	if (file.IsEmpty())
		return;

	int dlgType = GetDialogType();
	switch ( dlgType )
	{
	case ME_SELECTION_DIALOG: CProceduralCreationWorks::SelectBrowserClick(file);break;
	case ME_REPLACE_DIALOG: CProceduralCreationWorks::ReplaceBrowserClick(file);break;
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnEditorNotifyEvent( EEditorNotifyEvent aEvent )
{
	if (aEvent == eNotify_OnIdleUpdate)
	{
		//TODO: empty, reserved for thread folder structure load
	}
}

//////////////////////////////////////////////////////////////////////////
void CPanelTreeBrowser::OnFilterChange()
{
	CString filter;

	m_filter.GetWindowText( filter );
	m_treeCtrl.SetFilterText(filter);	
	ReselectLastSelectedTreeItem();
}
